Tutustu TypeScriptin tehokkaisiin enum-vaihtoehtoihin, kuten const-määrityksiin ja union-tyyppeihin. Opi niiden edut ja haitat puhtaamman ja ylläpidettävämmän koodin luomiseksi.
TypeScriptin Enum-vaihtoehdot: Const-määritykset ja Union-tyypit vakaan koodin rakentamisessa
TypeScript, voimakas JavaScriptin superjoukko, tuo staattisen tyypityksen web-kehityksen dynaamiseen maailmaan. Sen monien ominaisuuksien joukossa enum-avainsana on pitkään ollut suosittu tapa määritellä nimettyjä vakioita. Enumit tarjoavat selkeän tavan esittää kiinteä joukko toisiinsa liittyviä arvoja, parantaen luettavuutta ja tyyppiturvallisuutta.
Kuitenkin TypeScript-ekosysteemin kypsyessä ja projektien kasvaessa monimutkaisuudessa ja laajuudessa, kehittäjät maailmanlaajuisesti kyseenalaistavat yhä enemmän enumien perinteistä hyödyllisyyttä. Vaikka enumit ovat yksinkertaisia peruskäytössä, ne tuovat mukanaan tiettyjä käyttäytymismalleja ja ajoaikaisia ominaisuuksia, jotka voivat joskus johtaa odottamattomiin ongelmiin, vaikuttaa pakettikokoon tai monimutkaistaa tree-shaking-optimointeja. Tämä on johtanut laajamittaiseen vaihtoehtojen tutkimiseen.
Tämä kattava opas sukeltaa syvälle kahteen merkittävään ja erittäin tehokkaaseen vaihtoehtoon TypeScript-enumeille: Union-tyyppeihin merkkijono/numeroliteraaleilla ja Const-määrityksiin (as const). Tutkimme niiden mekanismeja, käytännön sovelluksia, etuja ja haittoja, antaen sinulle tiedot, joiden avulla voit tehdä perusteltuja suunnittelupäätöksiä projekteissasi, niiden koosta tai niitä kehittävästä globaalista tiimistä riippumatta. Tavoitteenamme on antaa sinulle valmiudet kirjoittaa vakaampaa, ylläpidettävämpää ja tehokkaampaa TypeScript-koodia.
TypeScriptin Enum: Lyhyt kertaus
Ennen kuin sukellamme vaihtoehtoihin, kerrataan lyhyesti perinteinen TypeScriptin enum. Enumien avulla kehittäjät voivat määritellä nimettyjä vakioita, mikä tekee koodista luettavampaa ja estää "maagisten merkkijonojen" tai "maagisten numeroiden" leviämisen sovellukseen. Ne esiintyvät kahdessa päämuodossa: numeerisina ja merkkijono-enumeina.
Numeeriset enumit
Oletuksena TypeScriptin enumit ovat numeerisia. Ensimmäinen jäsen alustetaan arvolla 0, ja jokainen seuraava jäsen saa automaattisesti seuraavan kokonaisluvun.
enum Direction {
Up,
Down,
Left,
Right,
}
let currentDirection: Direction = Direction.Up;
console.log(currentDirection); // Tulostaa: 0
console.log(Direction.Left); // Tulostaa: 2
Voit myös alustaa numeeristen enumien jäseniä manuaalisesti:
enum StatusCode {
Success = 200,
NotFound = 404,
ServerError = 500,
}
let status: StatusCode = StatusCode.NotFound;
console.log(status); // Tulostaa: 404
Numeeristen enumien erikoinen piirre on käänteinen mappaus. Ajoaikana numeerinen enum kääntyy JavaScript-olioksi, joka mappaa sekä nimet arvoihin että arvot takaisin nimiin.
enum UserRole {
Admin = 1,
Editor,
Viewer,
}
console.log(UserRole[1]); // Tulostaa: "Admin"
console.log(UserRole.Editor); // Tulostaa: 2
console.log(UserRole[2]); // Tulostaa: "Editor"
/*
Kääntyy JavaScriptiksi:
var UserRole;
(function (UserRole) {
UserRole[UserRole["Admin"] = 1] = "Admin";
UserRole[UserRole["Editor"] = 2] = "Editor";
UserRole[UserRole["Viewer"] = 3] = "Viewer";
})(UserRole || (UserRole = {}));
*/
Merkkijono-enumit
Merkkijono-enumit ovat usein suositumpia niiden ajoaikaisen luettavuuden vuoksi, koska ne eivät perustu automaattisesti kasvaviin numeroihin. Jokainen jäsen on alustettava merkkijonoliteraalilla.
enum UserPermission {
Read = "READ_PERMISSION",
Write = "WRITE_PERMISSION",
Delete = "DELETE_PERMISSION",
}
let permission: UserPermission = UserPermission.Write;
console.log(permission); // Tulostaa: "WRITE_PERMISSION"
Merkkijono-enumit eivät saa käänteistä mappausta, mikä on yleensä hyvä asia odottamattoman ajoaikaisen käyttäytymisen välttämiseksi ja generoidun JavaScript-koodin vähentämiseksi.
Enumien keskeiset näkökohdat ja mahdolliset sudenkuopat
Vaikka enumit ovat käteviä, niihin liittyy tiettyjä ominaisuuksia, jotka vaativat tarkkaa harkintaa:
- Ajoaikaiset oliot: Sekä numeeriset että merkkijono-enumit luovat JavaScript-olioita ajoaikana. Tämä tarkoittaa, että ne kasvattavat sovelluksesi pakettikokoa, vaikka käyttäisit niitä vain tyyppitarkistukseen. Pienissä projekteissa tämä voi olla merkityksetöntä, mutta suurissa sovelluksissa, joissa on paljon enumeita, se voi kertyä.
- Puutteellinen Tree-Shaking: Koska enumit ovat ajoaikaisia olioita, modernit paketoijat, kuten Webpack tai Rollup, eivät usein pysty tehokkaasti poistamaan niitä tree-shaking-prosessissa. Jos määrittelet enumin, mutta käytät vain yhtä tai kahta sen jäsentä, koko enum-olio saatetaan silti sisällyttää lopulliseen pakettiisi. Tämä voi johtaa tarpeettoman suuriin tiedostokokoihin.
- Käänteinen mappaus (numeeriset enumit): Numeeristen enumien käänteinen mappaus, vaikka joskus hyödyllinen, voi myös aiheuttaa sekaannusta ja odottamatonta käyttäytymistä. Se lisää ylimääräistä koodia JavaScript-ulostuloon, eikä se välttämättä ole aina toivottu toiminnallisuus. Esimerkiksi numeeristen enumien sarjallistaminen voi joskus johtaa vain numeron tallentamiseen, mikä ei välttämättä ole yhtä kuvaavaa kuin merkkijono.
- Transpiloinnin ylikuorma: Enumien kääntäminen JavaScript-olioiksi lisää pienen ylikuorman rakennusprosessiin verrattuna pelkkien vakiomuuttujien määrittelyyn.
- Rajoitettu iterointi: Enum-arvojen suora iterointi voi olla hankalaa, erityisesti numeeristen enumien kanssa käänteisen mappauksen vuoksi. Tarvitset usein apufunktioita tai erityisiä silmukoita saadaksesi vain halutut arvot.
Nämä seikat korostavat, miksi monet globaalit kehitystiimit, erityisesti ne, jotka keskittyvät suorituskykyyn ja pakettikokoon, etsivät vaihtoehtoja, jotka tarjoavat samanlaisen tyyppiturvallisuuden ilman ajoaikaista jalanjälkeä tai muita monimutkaisuuksia.
Vaihtoehto 1: Union-tyypit literaaleilla
Yksi suoraviivaisimmista ja tehokkaimmista vaihtoehdoista TypeScriptin enumeille on Union-tyyppien käyttö merkkijono- tai numeroliteraaleilla. Tämä lähestymistapa hyödyntää TypeScriptin vankkaa tyyppijärjestelmää määrittämään joukon sallittuja arvoja käännösaikana ilman, että se tuo uusia rakenteita ajonaikaan.
Mitä ovat Union-tyypit?
Union-tyyppi kuvaa arvoa, joka voi olla yksi useista tyypeistä. Esimerkiksi string | number tarkoittaa, että muuttuja voi sisältää joko merkkijonon tai numeron. Kun tämä yhdistetään literaalityyppeihin (esim. "success", 404), voit määritellä tyypin, joka voi sisältää vain tietyn ennalta määritellyn joukon arvoja.
Käytännön esimerkki: Tilojen määrittely Union-tyypeillä
Tarkastellaan yleistä skenaariota: tietojenkäsittelytyön tai käyttäjän tilin mahdollisten tilojen määrittelyä. Union-tyypeillä tämä näyttää siistiltä ja ytimekkäältä:
type JobStatus = "PENDING" | "IN_PROGRESS" | "COMPLETED" | "FAILED";
function processJob(status: JobStatus): void {
if (status === "COMPLETED") {
console.log("Työ valmistui onnistuneesti.");
} else if (status === "FAILED") {
console.log("Työssä tapahtui virhe.");
} else {
console.log(`Työn tila on tällä hetkellä ${status}.`);
}
}
let currentJobStatus: JobStatus = "IN_PROGRESS";
processJob(currentJobStatus);
// Tämä aiheuttaisi käännösaikaisen virheen:
// let invalidStatus: JobStatus = "CANCELLED"; // Error: Type '"CANCELLED"' is not assignable to type 'JobStatus'.
Numeerisille arvoille malli on identtinen:
type HttpCode = 200 | 400 | 404 | 500;
function handleResponse(code: HttpCode): void {
if (code === 200) {
console.log("Toiminto onnistui.");
} else if (code === 404) {
console.log("Resurssia ei löytynyt.");
}
}
let responseStatus: HttpCode = 200;
handleResponse(responseStatus);
Huomaa, kuinka määrittelemme tässä type-aliaksen. Tämä on puhtaasti käännösaikainen rakenne. Kun koodi käännetään JavaScriptiksi, JobStatus yksinkertaisesti katoaa, ja literaalisia merkkijonoja/numeroita käytetään suoraan.
Union-tyyppien ja literaalien edut
Tämä lähestymistapa tarjoaa useita merkittäviä etuja:
- Puhtaasti käännösaikainen: Union-tyypit poistetaan kokonaan käännöksen aikana. Ne eivät generoi lainkaan JavaScript-koodia ajonaikana, mikä johtaa pienempiin pakettikokoihin ja nopeampiin sovelluksen käynnistymisaikoihin. Tämä on merkittävä etu suorituskykykriittisissä sovelluksissa ja globaalisti jaelluissa sovelluksissa, joissa jokainen kilotavu on tärkeä.
- Erinomainen tyyppiturvallisuus: TypeScript tarkistaa sijoitukset tiukasti määriteltyjä literaalityyppejä vastaan, mikä takaa vahvasti, että vain sallittuja arvoja käytetään. Tämä estää yleisiä virheitä, jotka johtuvat kirjoitusvirheistä tai vääristä arvoista.
- Optimaalinen Tree-Shaking: Koska ajoaikaista oliota ei ole, union-tyypit tukevat luonnostaan tree-shakingia. Paketoijasi sisällyttää vain ne merkkijono- tai numeroliteraalit, joita käytät, ei koko oliota.
- Luettavuus: Kiinteälle joukolle yksinkertaisia, erillisiä arvoja tyyppimäärittely on usein hyvin selkeä ja helposti ymmärrettävä.
- Yksinkertaisuus: Uusia kielirakenteita tai monimutkaisia käännösartefakteja ei synny. Kyse on vain TypeScriptin perustyyppiominaisuuksien hyödyntämisestä.
- Suora arvojen käyttö: Työskentelet suoraan merkkijono- tai numeroarvojen kanssa, mikä yksinkertaistaa sarjallistamista ja desarjallistamista, erityisesti kun ollaan vuorovaikutuksessa API-rajapintojen tai tietokantojen kanssa, jotka odottavat tiettyjä merkkijonotunnisteita.
Union-tyyppien ja literaalien haitat
Vaikka union-tyypit ovat tehokkaita, niillä on myös joitain rajoituksia:
- Toisto liitännäisdatan kanssa: Jos sinun täytyy liittää lisätietoja tai metadataa jokaiseen "enum"-jäseneen (esim. näytettävä nimi, ikoni, väri), et voi tehdä sitä suoraan union-tyypin määrittelyssä. Tarvitsisit tyypillisesti erillisen mappausolion.
- Ei suoraa kaikkien arvojen iterointia: Ei ole sisäänrakennettua tapaa saada kaikkien mahdollisten arvojen taulukkoa union-tyypistä ajonaikana. Et voi esimerkiksi helposti saada
["PENDING", "IN_PROGRESS", "COMPLETED", "FAILED"]suoraanJobStatus-tyypistä. Tämä vaatii usein erillisen arvojen taulukon ylläpitoa, jos ne täytyy näyttää käyttöliittymässä (esim. pudotusvalikossa). - Vähemmän keskitetty: Jos arvojen joukkoa tarvitaan sekä tyyppinä että ajonaikaisena taulukkona, saatat joutua määrittelemään listan kahdesti (kerran tyyppinä, kerran ajonaikaisena taulukkona), mikä voi aiheuttaa synkronointiongelmia.
Näistä haitoista huolimatta monissa skenaarioissa union-tyypit tarjoavat siistin, suorituskykyisen ja tyyppiturvallisen ratkaisun, joka sopii hyvin modernin JavaScript-kehityksen käytäntöihin.
Vaihtoehto 2: Const-määritykset (as const)
as const -määritys, joka esiteltiin TypeScript 3.4:ssä, on toinen uskomattoman tehokas työkalu, joka tarjoaa erinomaisen vaihtoehdon enumeille, erityisesti kun tarvitaan ajoaikaista oliota ja vankkaa tyyppipäättelyä. Se antaa TypeScriptin päätellä mahdollisimman tarkan tyypin literaaleille.
Mitä ovat Const-määritykset?
Kun käytät as const -määritystä muuttujaan, taulukkoon tai olion literaaliin, TypeScript käsittelee kaikkia literaalin sisällä olevia ominaisuuksia readonly-ominaisuuksina ja päättelee niiden literaalityypit laajempien tyyppien sijaan (esim. "foo" string-tyypin sijaan, 123 number-tyypin sijaan). Tämä mahdollistaa erittäin tarkkojen union-tyyppien johtamisen ajonaikaisista tietorakenteista.
Käytännön esimerkki: "Pseudo-Enum"-olion luominen as const -määrityksellä
Palataan työntila-esimerkkiimme. as const -määrityksellä voimme määritellä yhden totuuden lähteen tiloillemme, joka toimii sekä ajonaikaisena oliona että tyyppimääritysten perustana.
const JobStatuses = {
PENDING: "PENDING",
IN_PROGRESS: "IN_PROGRESS",
COMPLETED: "COMPLETED",
FAILED: "FAILED",
} as const;
// JobStatuses.PENDING päätellään nyt tyypiksi "PENDING" (ei vain string)
// JobStatuses päätellään tyypiksi {
// readonly PENDING: "PENDING";
// readonly IN_PROGRESS: "IN_PROGRESS";
// readonly COMPLETED: "COMPLETED";
// readonly FAILED: "FAILED";
// }
Tässä vaiheessa JobStatuses on JavaScript-olio ajonaikana, aivan kuten tavallinen enum. Sen tyyppipäättely on kuitenkin paljon tarkempaa.
Yhdistäminen typeof- ja keyof-operaattoreihin Union-tyyppien luomiseksi
Todellinen voima tulee esiin, kun yhdistämme as const -määrityksen TypeScriptin typeof- ja keyof-operaattoreihin johtaaksemme union-tyypin olion arvoista tai avaimista.
const JobStatuses = {
PENDING: "PENDING",
IN_PROGRESS: "IN_PROGRESS",
COMPLETED: "COMPLETED",
FAILED: "FAILED",
} as const;
// Tyyppi, joka edustaa avaimia (esim. "PENDING" | "IN_PROGRESS" | ...)
type JobStatusKeys = keyof typeof JobStatuses;
// Tyyppi, joka edustaa arvoja (esim. "PENDING" | "IN_PROGRESS" | ...)
type JobStatusValues = typeof JobStatuses[keyof typeof JobStatuses];
function processJobWithConstAssertion(status: JobStatusValues): void {
if (status === JobStatuses.COMPLETED) {
console.log("Työ valmistui onnistuneesti.");
} else if (status === JobStatuses.FAILED) {
console.log("Työssä tapahtui virhe.");
} else {
console.log(`Työn tila on tällä hetkellä ${status}.`);
}
}
let currentJobStatusFromObject: JobStatusValues = JobStatuses.IN_PROGRESS;
processJobWithConstAssertion(currentJobStatusFromObject);
// Tämä aiheuttaisi käännösaikaisen virheen:
// let invalidStatusFromObject: JobStatusValues = "CANCELLED"; // Virhe!
Tämä malli tarjoaa parhaat puolet molemmista maailmoista: ajonaikaisen olion iterointiin tai suoraan ominaisuuksien käyttöön, ja käännösaikaisen union-tyypin tiukkaan tyyppitarkistukseen.
Const-määritysten ja johdettujen Union-tyyppien edut
- Yksi totuuden lähde: Määrittelet vakiot kerran tavallisessa JavaScript-oliossa ja johdat siitä sekä ajonaikaisen käytön että käännösaikaiset tyypit. Tämä vähentää merkittävästi toistoa ja parantaa ylläpidettävyyttä monikansallisissa kehitystiimeissä.
- Tyyppiturvallisuus: Kuten puhtailla union-tyypeillä, saat erinomaisen tyyppiturvallisuuden, joka varmistaa, että vain ennalta määriteltyjä arvoja käytetään.
- Iteroitavuus ajonaikana: Koska
JobStatuseson tavallinen JavaScript-olio, voit helposti iteroida sen avainten tai arvojen yli käyttämällä standardeja JavaScript-metodeja, kutenObject.keys(),Object.values()taiObject.entries(). Tämä on korvaamatonta dynaamisissa käyttöliittymissä (esim. pudotusvalikoiden täyttämisessä) tai lokituksessa. - Liitännäisdata: Tämä malli tukee luonnollisesti lisätietojen liittämistä jokaiseen "enum"-jäseneen.
- Parempi Tree-Shaking-potentiaali (verrattuna enumeihin): Vaikka
as constluo ajonaikaisen olion, se on standardi JavaScript-olio. Modernit paketoijat ovat yleensä tehokkaampia poistamaan käyttämättömiä ominaisuuksia tai jopa kokonaisia olioita, jos niihin ei viitata, verrattuna TypeScriptin enum-käännöstulokseen. Jos olio on kuitenkin suuri ja vain muutamaa ominaisuutta käytetään, koko olio saatetaan silti sisällyttää, jos se tuodaan tavalla, joka estää hienojakoisen tree-shakingin. - Joustavuus: Voit määritellä arvoja, jotka eivät ole vain merkkijonoja tai numeroita, vaan tarvittaessa monimutkaisempia olioita, mikä tekee tästä erittäin joustavan mallin.
const FileOperations = {
UPLOAD: {
label: "Lataa tiedosto",
icon: "upload-icon.svg",
permission: "can_upload"
},
DOWNLOAD: {
label: "Lataa tiedosto",
icon: "download-icon.svg",
permission: "can_download"
},
DELETE: {
label: "Poista tiedosto",
icon: "delete-icon.svg",
permission: "can_delete"
},
} as const;
type FileOperationType = keyof typeof FileOperations; // "UPLOAD" | "DOWNLOAD" | "DELETE"
type FileOperationDetail = typeof FileOperations[keyof typeof FileOperations]; // { label: string; icon: string; permission: string; }
function performOperation(opType: FileOperationType) {
const details = FileOperations[opType];
console.log(`Suoritetaan: ${details.label} (Lupa: ${details.permission})`);
}
performOperation("UPLOAD");
Const-määritysten haitat
- Ajoaikaisen olion olemassaolo: Toisin kuin puhtaat union-tyypit, tämä lähestymistapa luo edelleen JavaScript-olion ajonaikana. Vaikka se on standardi olio ja usein parempi tree-shakingin kannalta kuin enumit, se ei poistu kokonaan.
- Hieman monisanaisempi tyyppimäärittely: Union-tyypin johtaminen (
keyof typeof ...taitypeof ...[keyof typeof ...]) vaatii hieman enemmän syntaksia kuin pelkkä literaalien listaaminen union-tyypille. - Mahdollinen väärinkäyttö: Jos sitä ei käytetä huolellisesti, erittäin suuri
as const-olio voi silti kasvattaa merkittävästi pakettikokoa, jos sen sisältöä ei poisteta tehokkaasti tree-shaking-prosessissa moduulirajojen yli.
Skenaarioissa, joissa tarvitaan sekä vankkaa käännösaikaista tyyppitarkistusta että ajonaikaista arvojen kokoelmaa, jota voidaan iteroida tai joka voi tarjota liitännäisdataa, as const on usein TypeScript-kehittäjien suosima valinta maailmanlaajuisesti.
Vaihtoehtojen vertailu: Milloin käyttää mitäkin?
Valinta union-tyyppien ja const-määritysten välillä riippuu suurelta osin erityisvaatimuksistasi koskien ajonaikaista olemassaoloa, iteroitavuutta ja sitä, tarvitsetko liittää lisätietoja vakioihisi. Käydään läpi päätöksentekoon vaikuttavat tekijät.
Yksinkertaisuus vs. vankkuus
- Union-tyypit: Tarjoavat äärimmäistä yksinkertaisuutta, kun tarvitset vain tyyppiturvallisen joukon erillisiä merkkijono- tai numeroarvoja käännösaikana. Ne ovat kevyin vaihtoehto.
- Const-määritykset: Tarjoavat vankemman mallin, kun tarvitset sekä käännösaikaista tyyppiturvallisuutta että ajonaikaista oliota, jota voidaan kysellä, iteroida tai laajentaa lisämetadatalla. Alkuasennus on hieman monisanaisempi, mutta se maksaa itsensä takaisin ominaisuuksissa.
Ajoaikainen vs. käännösaikainen olemassaolo
- Union-tyypit: Ovat puhtaasti käännösaikaisia rakenteita. Ne eivät generoi lainkaan JavaScript-koodia. Tämä on ihanteellista sovelluksille, joissa pakettikoon minimointi on ensisijaista, ja arvot itsessään riittävät ilman, että niitä tarvitsee käyttää oliona ajonaikana.
- Const-määritykset: Generoivat tavallisen JavaScript-olion ajonaikana. Tämä olio on käytettävissä JavaScript-koodissasi. Vaikka se lisää pakettikokoa, se on yleensä tehokkaampi kuin TypeScript-enumit ja parempi ehdokas tree-shakingille.
Iteroitavuusvaatimukset
- Union-tyypit: Eivät tarjoa suoraa tapaa iteroida kaikkia mahdollisia arvoja ajonaikana. Jos sinun täytyy täyttää pudotusvalikko tai näyttää kaikki vaihtoehdot, sinun on määriteltävä erillinen taulukko näistä arvoista, mikä voi johtaa toistoon.
- Const-määritykset: Loistavat tässä. Koska työskentelet standardin JavaScript-olion kanssa, voit helposti käyttää
Object.keys(),Object.values()taiObject.entries()saadaksesi taulukon avaimista, arvoista tai avain-arvo-pareista. Tämä tekee niistä täydellisiä dynaamisiin käyttöliittymiin tai mihin tahansa skenaarioon, joka vaatii ajonaikaista luettelointia.
const PaymentMethods = {
CREDIT_CARD: "Luottokortti",
PAYPAL: "PayPal",
BANK_TRANSFER: "Pankkisiirto",
} as const;
type PaymentMethodType = keyof typeof PaymentMethods;
// Hae kaikki avaimet (esim. sisäiseen logiikkaan)
const methodKeys = Object.keys(PaymentMethods) as PaymentMethodType[];
console.log(methodKeys); // ["CREDIT_CARD", "PAYPAL", "BANK_TRANSFER"]
// Hae kaikki arvot (esim. näytettäväksi pudotusvalikossa)
const methodLabels = Object.values(PaymentMethods);
console.log(methodLabels); // ["Luottokortti", "PayPal", "Pankkisiirto"]
// Hae avain-arvo-parit (esim. mappaukseen)
const methodEntries = Object.entries(PaymentMethods);
console.log(methodEntries); // [["CREDIT_CARD", "Luottokortti"], ...]
Tree-Shaking-vaikutukset
- Union-tyypit: Ovat luonnostaan tree-shake-ystävällisiä, koska ne ovat vain käännösaikaisia.
- Const-määritykset: Vaikka ne luovat ajonaikaisen olion, modernit paketoijat voivat usein poistaa tämän olion käyttämättömiä ominaisuuksia tehokkaammin kuin TypeScriptin generoimien enum-olioiden kanssa. Jos koko olio kuitenkin tuodaan ja siihen viitataan, se todennäköisesti sisällytetään. Huolellinen moduulisuunnittelu voi auttaa.
Parhaat käytännöt ja hybridimallit
Se ei ole aina "joko/tai"-tilanne. Usein paras ratkaisu sisältää hybridimallin, erityisesti suurissa, kansainvälistetyissä sovelluksissa:
- Yksinkertaisille, puhtaasti sisäisille lipuille tai tunnisteille, joita ei koskaan tarvitse iteroida tai joilla ei ole liitännäisdataa, Union-tyypit ovat yleensä suorituskykyisin ja siistein valinta.
- Vakiojoukoille, jotka täytyy iteroida, näyttää käyttöliittymissä tai joilla on runsaasti liitännäismetadataa (kuten nimiä, ikoneita tai käyttöoikeuksia), Const-määritysmalli on ylivoimainen.
- Yhdistäminen luettavuuden ja lokalisoinnin vuoksi: Monet tiimit käyttävät
as const-määritystä sisäisiin tunnisteisiin ja johtavat sitten lokalisoidut näyttönimet erillisestä kansainvälistämisjärjestelmästä (i18n).
// src/constants/order-status.ts
const OrderStatuses = {
PENDING: "PENDING",
PROCESSING: "PROCESSING",
SHIPPED: "SHIPPED",
DELIVERED: "DELIVERED",
CANCELLED: "CANCELLED",
} as const;
type OrderStatus = typeof OrderStatuses[keyof typeof OrderStatuses];
export { OrderStatuses, type OrderStatus };
// src/i18n/fi.json
{
"orderStatus": {
"PENDING": "Odottaa vahvistusta",
"PROCESSING": "Käsitellään tilausta",
"SHIPPED": "Lähetetty",
"DELIVERED": "Toimitettu",
"CANCELLED": "Peruutettu"
}
}
// src/components/OrderStatusDisplay.tsx
import { OrderStatuses, type OrderStatus } from "../constants/order-status";
import { useTranslation } from "react-i18next"; // Esimerkki i18n-kirjastosta
interface OrderStatusDisplayProps {
status: OrderStatus;
}
function OrderStatusDisplay({ status }: OrderStatusDisplayProps) {
const { t } = useTranslation();
const displayLabel = t(`orderStatus.${status}`);
return <span>Tila: {displayLabel}</span>;
}
// Käyttö:
// <OrderStatusDisplay status={OrderStatuses.DELIVERED} />
Tämä hybridimalli hyödyntää as const -määrityksen tyyppiturvallisuutta ja ajonaikaista iteroitavuutta samalla, kun se pitää lokalisoidut näyttömerkkijonot erillisinä ja hallittavina, mikä on kriittinen näkökohta globaaleissa sovelluksissa.
Edistyneet mallit ja näkökohdat
Peruskäytön lisäksi sekä union-tyyppejä että const-määrityksiä voidaan integroida kehittyneempiin malleihin koodin laadun ja ylläpidettävyyden parantamiseksi entisestään.
Tyyppivahtien käyttö Union-tyyppien kanssa
Kun työskennellään union-tyyppien kanssa, erityisesti kun unioni sisältää erilaisia tyyppejä (ei vain literaaleja), tyyppivahdeista tulee välttämättömiä tyyppien kaventamiseksi. Literaalisten union-tyyppien kanssa erotellut unionit tarjoavat valtavasti tehoa.
type SuccessEvent = { type: "SUCCESS"; data: any; };
type ErrorEvent = { type: "ERROR"; message: string; code: number; };
type SystemEvent = SuccessEvent | ErrorEvent;
function handleSystemEvent(event: SystemEvent) {
if (event.type === "SUCCESS") {
console.log("Data vastaanotettu:", event.data);
// event on nyt kavennettu SuccessEvent-tyypiksi
} else {
console.log("Tapahtui virhe:", event.message, "Koodi:", event.code);
// event on nyt kavennettu ErrorEvent-tyypiksi
}
}
handleSystemEvent({ type: "SUCCESS", data: { user: "Alice" } });
handleSystemEvent({ type: "ERROR", message: "Verkkovirhe", code: 503 });
Tämä malli, jota usein kutsutaan "erotelluiksi unioneiksi", on uskomattoman vankka ja tyyppiturvallinen, tarjoten käännösaikaisia takuita datan rakenteesta yhteisen literaaliominaisuuden (erottelijan) perusteella.
Object.values() `as const` -määrityksen ja tyyppivakuutusten kanssa
Kun käytetään as const -mallia, Object.values() voi olla erittäin hyödyllinen. TypeScriptin oletuspäättely Object.values():lle saattaa kuitenkin olla laajempi kuin toivottu (esim. string[] tietyn literaaliunionin sijaan). Saatat tarvita tyyppivakuutuksen tiukkuuden varmistamiseksi.
const Statuses = {
ACTIVE: "Aktiivinen",
INACTIVE: "Ei-aktiivinen",
PENDING: "Odottaa",
} as const;
type StatusValue = typeof Statuses[keyof typeof Statuses]; // "Aktiivinen" | "Ei-aktiivinen" | "Odottaa"
// Object.values(Statuses) päätellään tyypiksi (string | "Aktiivinen" | "Ei-aktiivinen" | "Odottaa")[]
// Voimme vakuuttaa sen tarkemmaksi tarvittaessa:
const allStatusValues: StatusValue[] = Object.values(Statuses);
console.log(allStatusValues); // ["Aktiivinen", "Ei-aktiivinen", "Odottaa"]
// Pudotusvalikkoa varten voit yhdistää arvot nimiin, jos ne eroavat
const statusOptions = Object.entries(Statuses).map(([key, value]) => ({
value: key, // Käytä avainta todellisena tunnisteena
label: value // Käytä arvoa näyttönimenä
}));
console.log(statusOptions);
/*
[
{ value: "ACTIVE", label: "Aktiivinen" },
{ value: "INACTIVE", label: "Ei-aktiivinen" },
{ value: "PENDING", label: "Odottaa" }
]
*/
Tämä osoittaa, kuinka saada vahvasti tyypitetty taulukko arvoista, jotka soveltuvat käyttöliittymäelementeille, säilyttäen samalla literaalityypit.
Kansainvälistäminen (i18n) ja lokalisoidut etiketit
Globaaleissa sovelluksissa lokalisoitujen merkkijonojen hallinta on ensisijaisen tärkeää. Vaikka TypeScript-enumit ja niiden vaihtoehdot tarjoavat sisäisiä tunnisteita, näyttönimet on usein erotettava i18n-tarkoituksiin. as const -malli täydentää kauniisti i18n-järjestelmiä.
Määrittelet sisäiset, muuttumattomat tunnisteet käyttämällä as const -määritystä. Nämä tunnisteet ovat johdonmukaisia kaikissa lokaaleissa ja toimivat avaimina käännöstiedostoillesi. Varsinaiset näyttömerkkijonot haetaan sitten i18n-kirjastosta (esim. react-i18next, vue-i18n, FormatJS) käyttäjän valitseman kielen perusteella.
// app/features/product/constants.ts
export const ProductCategories = {
ELECTRONICS: "ELECTRONICS",
APPAREL: "APPAREL",
HOME_GOODS: "HOME_GOODS",
BOOKS: "BOOKS",
} as const;
export type ProductCategory = typeof ProductCategories[keyof typeof ProductCategories];
// app/i18n/locales/fi.json
{
"productCategories": {
"ELECTRONICS": "Elektroniikka",
"APPAREL": "Vaatteet ja asusteet",
"HOME_GOODS": "Kodin tarvikkeet",
"BOOKS": "Kirjat"
}
}
// app/i18n/locales/es.json
{
"productCategories": {
"ELECTRONICS": "Electrónica",
"APPAREL": "Ropa y Accesorios",
"HOME_GOODS": "Artículos para el hogar",
"BOOKS": "Libros"
}
}
// app/components/ProductCategorySelector.tsx
import { ProductCategories, type ProductCategory } from "../features/product/constants";
import { useTranslation } from "react-i18next";
function ProductCategorySelector() {
const { t } = useTranslation();
return (
<select>
{Object.values(ProductCategories).map(categoryKey => (
<option key={categoryKey} value={categoryKey}>
{t(`productCategories.${categoryKey}`)}
</option>
))}
</select>
);
}
Tämä vastuualueiden erottelu on ratkaisevan tärkeää skaalautuville, globaaleille sovelluksille. TypeScript-tyypit varmistavat, että käytät aina kelvollisia avaimia, ja i18n-järjestelmä hoitaa esityskerroksen käyttäjän lokaalin perusteella. Tämä estää kieliriippuvaisten merkkijonojen upottamisen suoraan sovelluslogiikan ytimeen, mikä on yleinen antipatterni kansainvälisissä tiimeissä.
Johtopäätös: Vahvista TypeScript-suunnitteluvalintojasi
TypeScriptin jatkaessa kehittymistään ja antaessa kehittäjille ympäri maailmaa valmiuksia rakentaa vankempia ja skaalautuvampia sovelluksia, sen hienovaraisten ominaisuuksien ja vaihtoehtojen ymmärtäminen tulee yhä tärkeämmäksi. Vaikka TypeScriptin enum-avainsana tarjoaa kätevän tavan määritellä nimettyjä vakioita, sen ajoaikainen jalanjälki, tree-shaking-rajoitukset ja käänteisen mappauksen monimutkaisuus tekevät moderneista vaihtoehdoista usein houkuttelevampia suorituskykyherkissä tai suurissa projekteissa.
Union-tyypit merkkijono/numeroliteraaleilla erottuvat kevyimpänä ja eniten käännösaikaan keskittyvänä ratkaisuna. Ne tarjoavat tinkimättömän tyyppiturvallisuuden generoimatta lainkaan JavaScript-koodia ajonaikana, mikä tekee niistä ihanteellisia skenaarioihin, joissa minimaalinen pakettikoko ja maksimaalinen tree-shaking ovat prioriteetteja, eikä ajonaikainen luettelointi ole huolenaihe.
Toisaalta Const-määritykset (as const) yhdistettynä typeof- ja keyof-operaattoreihin tarjoavat erittäin joustavan ja tehokkaan mallin. Ne tarjoavat yhden totuuden lähteen vakioillesi, vahvan käännösaikaisen tyyppiturvallisuuden ja kriittisen kyvyn iteroida arvojen yli ajonaikana. Tämä lähestymistapa sopii erityisen hyvin tilanteisiin, joissa sinun on liitettävä lisätietoja vakioihisi, täytettävä dynaamisia käyttöliittymiä tai integroitava saumattomasti kansainvälistämisjärjestelmiin.
Harkitsemalla huolellisesti kompromisseja – ajoaikainen jalanjälki, iteroitavuustarpeet ja liitännäisdatan monimutkaisuus – voit tehdä perusteltuja päätöksiä, jotka johtavat puhtaampaan, tehokkaampaan ja ylläpidettävämpään TypeScript-koodiin. Näiden vaihtoehtojen omaksuminen ei ole vain "modernin" TypeScriptin kirjoittamista; se on tietoisten arkkitehtonisten valintojen tekemistä, jotka parantavat sovelluksesi suorituskykyä, kehittäjäkokemusta ja pitkän aikavälin kestävyyttä globaalille yleisölle.
Vahvista TypeScript-kehitystäsi valitsemalla oikea työkalu oikeaan tehtävään ja siirtymällä oletusarvoisen enumin käytöstä, kun parempia vaihtoehtoja on olemassa.